package magnet.processor.instances.parser;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import magnet.Classifier;
import magnet.Instance;
import magnet.processor.MagnetProcessorEnv;
import magnet.processor.common.CompilationException;
import magnet.processor.common.KotlinMethodMetadata;
import magnet.processor.common.ValidationException;
import magnet.processor.instances.*;

import javax.lang.model.element.*;
import java.util.ArrayList;
import java.util.List;

public class InstanceParserForMethod extends InstanceParser<ExecutableElement>{

    public InstanceParserForMethod(MagnetProcessorEnv env) {
        super(env, false);
    }

    @Override
    protected List<FactoryType> generateFactories(ParserInstance<ExecutableElement> instance) throws ValidationException, CompilationException {
        ExecutableElement element = instance.getElement();
        TypeName staticMethodReturnType = TypeName.get(element.getReturnType());
        for (ClassName type : instance.getTypes()) {
            //TODO 此处转换不确定是否正确
            if (!type.equals(staticMethodReturnType)) {
                if (staticMethodReturnType instanceof ParameterizedTypeName) {
                    if (instance.getClassifier() == Classifier.NONE) {
                        throw new ValidationException(element,
                                "Method providing a parametrised type must have 'classifier' value" +
                                        " set in @" + Instance.class.getSimpleName() + " annotation.");
                    }
                } else {
                    throw new ValidationException(element,
                            "Method must return instance of " + type.reflectionName() + " as declared" +
                                    " by @" + Instance.class.getSimpleName() + " annotation." +
                                    " Returned type: " + staticMethodReturnType + ".");
                }
            }
        }

        ClassName staticMethodClassName = ClassName.get((TypeElement) element.getEnclosingElement());
        String staticMethodName = element.getSimpleName().toString();
        StringBuilder uniqueFactoryNameBuilder = new StringBuilder()
                .append(staticMethodClassName.packageName())
                .append('.')
                .append(capitalize(staticMethodClassName.simpleName()))
                .append(capitalize(staticMethodName));

        TypeElement topmostElement = getTopmostTypeElement(element);

        KotlinMethodMetadata methodMeta = null;

        List<MethodParameter> methodParameters = new ArrayList<>();
        for (VariableElement variable : element.getParameters()) {
            MethodParameter methodParameter = parseMethodParameter(element, variable, methodMeta);
            methodParameters.add(methodParameter);
            uniqueFactoryNameBuilder.append(capitalize(methodParameter.getName()));
        }

        String instanceFullName = uniqueFactoryNameBuilder.toString();

        List<FactoryType> result = new ArrayList<>();

        for (ClassName className : instance.getTypes()) {
            boolean isSingleTypeFactory = instance.getTypes().size() == 1;
            GetSiblingTypesMethod getSiblingTypesMethod = null;
            if (!isSingleTypeFactory) {
                ArrayList<ClassName> types = new ArrayList<>();
                types.addAll(instance.getTypes());
                types.remove(className);
                List<ClassName> siblingTypes = new ArrayList<>();
                for (ClassName type : types) {
                    siblingTypes.add(type);
                    String factoryFullName = generateFactoryName(false, instanceFullName, type);
                    siblingTypes.add(ClassName.bestGuess(factoryFullName));
                }
                getSiblingTypesMethod = new GetSiblingTypesMethod(siblingTypes);
            }

            List<String> selectorAttributes = instance.getSelector();
            GetSelectorMethod getSelectorMethod = null;
            if (selectorAttributes != null) {
                getSelectorMethod = new GetSelectorMethod(selectorAttributes);
            }
            GetLimitMethod getLimitMethod = null;
            if (!instance.getLimitedTo().isEmpty()) {
                getLimitMethod = new GetLimitMethod(instance.getLimitedTo());
            }
            String factoryFullName = generateFactoryName(isSingleTypeFactory, instanceFullName, className);
            FactoryType factoryType = new FactoryType(
                    element,
                    className,
                    instance.getClassifier(),
                    instance.getScoping(),
                    instance.isDisabled(),
                    ClassName.bestGuess(factoryFullName),
                    null,
                    instance.getFactory(),
                    instance.getDisposer(),
                    new StaticMethodCreateStatement(staticMethodClassName, staticMethodName),
                    new CreateMethod(methodParameters),
                    new GetScopingMethod(instance.getScoping()),
                    getLimitMethod,
                    getSelectorMethod,
                    getSiblingTypesMethod
            );
            result.add(factoryType);
        }

        return result;
    }

    @Override
    protected void onBeforeParsing(ExecutableElement element) throws ValidationException {
        if (!element.getModifiers().contains(Modifier.STATIC)) {
            throw new ValidationException(element,
                    "Method annotated by " + Instance.class + " must be 'static'");
        }

        if (element.getModifiers().contains(Modifier.PRIVATE)) {
            throw new ValidationException(element,
                    "Method annotated by " + Instance.class + " must not be 'private'");
        }
    }

    private String capitalize(String str) {
        if (str != null && !str.isEmpty()) {
            String first = str.substring(0, 1).toUpperCase();
            return first + str.substring(1);
        }
        return str;
    }

    private TypeElement getTopmostTypeElement(Element mElement) throws CompilationException {
        TypeElement result = null;
        Element element = mElement;
        while (element != null) {
            if (element instanceof TypeElement) {
                result = (TypeElement) element;
            }
            element = element.getEnclosingElement();
        }
        if (result != null) {
            return result;
        } else {
            throw new CompilationException(element,
                    "Static method must be declared in a class.");
        }
    }

    private String generateFactoryName(boolean isSingleTypeFactory, String instanceName, ClassName it) {
        if (isSingleTypeFactory) {
            return instanceName + "MagnetFactory";
        } else {
            return instanceName + it.simpleName() + "MagnetFactory";
        }
    }
}
